use std::fmt::Display;

use crate::util;

/// KeywordType represents the type of a keyword in the Jack language.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum KeywordType {
    Class,
    Method,
    Function,
    Constructor,
    Int,
    Boolean,
    Char,
    Void,
    Var,
    Static,
    Field,
    Let,
    Do,
    If,
    Else,
    While,
    Return,
    True,
    False,
    Null,
    This,
}

impl KeywordType {
    /// to xml tag
    pub(crate) fn to_xml(self) -> String {
        format!("<keyword>{}</keyword>", self.to_str())
    }
    /// Returns the string representation of the keyword type.
    pub(crate) fn to_str(self) -> &'static str {
        match self {
            Self::Class => "class",
            Self::Method => "method",
            Self::Function => "function",
            Self::Constructor => "constructor",
            Self::Int => "int",
            Self::Boolean => "boolean",
            Self::Char => "char",
            Self::Void => "void",
            Self::Var => "var",
            Self::Static => "static",
            Self::Field => "field",
            Self::Let => "let",
            Self::Do => "do",
            Self::If => "if",
            Self::Else => "else",
            Self::While => "while",
            Self::Return => "return",
            Self::True => "true",
            Self::False => "false",
            Self::Null => "null",
            Self::This => "this",
        }
    }

    pub(crate) fn parse(value: &str) -> Option<Self> {
        match value {
            "class" => Some(Self::Class),
            "method" => Some(Self::Method),
            "function" => Some(Self::Function),
            "constructor" => Some(Self::Constructor),
            "int" => Some(Self::Int),
            "boolean" => Some(Self::Boolean),
            "char" => Some(Self::Char),
            "void" => Some(Self::Void),
            "var" => Some(Self::Var),
            "static" => Some(Self::Static),
            "field" => Some(Self::Field),
            "let" => Some(Self::Let),
            "do" => Some(Self::Do),
            "if" => Some(Self::If),
            "else" => Some(Self::Else),
            "while" => Some(Self::While),
            "return" => Some(Self::Return),
            "true" => Some(Self::True),
            "false" => Some(Self::False),
            "null" => Some(Self::Null),
            "this" => Some(Self::This),
            _ => None,
        }
    }
}

/// SymbolType represents the type of a symbol in the Jack language.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SymbolType {
    /// {
    OpenBrace,
    /// }
    CloseBrace,
    /// (
    OpenParenthesis,
    /// )
    CloseParenthesis,
    /// [
    OpenBracket,
    /// ]
    CloseBracket,
    /// .
    Dot,
    /// ,
    Comma,
    /// ;
    Semicolon,
    /// +
    Plus,
    /// -
    Minus,
    /// *
    Multiply,
    /// /
    Divide,
    /// &
    And,
    /// |
    Or,
    /// <
    LessThan,
    /// >
    GreaterThan,
    /// =
    Equal,
    /// ~
    Not,
}

impl SymbolType {
    /// Returns the string representation of the symbol type.
    pub(crate) fn char(&self) -> char {
        match self {
            Self::OpenBrace => '{',
            Self::CloseBrace => '}',
            Self::OpenParenthesis => '(',
            Self::CloseParenthesis => ')',
            Self::OpenBracket => '[',
            Self::CloseBracket => ']',
            Self::Dot => '.',
            Self::Comma => ',',
            Self::Semicolon => ';',
            Self::Plus => '+',
            Self::Minus => '-',
            Self::Multiply => '*',
            Self::Divide => '/',
            Self::And => '&',
            Self::Or => '|',
            Self::LessThan => '<',
            Self::GreaterThan => '>',
            Self::Equal => '=',
            Self::Not => '~',
        }
    }
    /// Parses a string into a symbol type.
    pub(crate) fn parse(value: &char) -> Option<Self> {
        match value {
            '{' => Some(Self::OpenBrace),
            '}' => Some(Self::CloseBrace),
            '(' => Some(Self::OpenParenthesis),
            ')' => Some(Self::CloseParenthesis),
            '[' => Some(Self::OpenBracket),
            ']' => Some(Self::CloseBracket),
            '.' => Some(Self::Dot),
            ',' => Some(Self::Comma),
            ';' => Some(Self::Semicolon),
            '+' => Some(Self::Plus),
            '-' => Some(Self::Minus),
            '*' => Some(Self::Multiply),
            '/' => Some(Self::Divide),
            '&' => Some(Self::And),
            '|' => Some(Self::Or),
            '<' => Some(Self::LessThan),
            '>' => Some(Self::GreaterThan),
            '=' => Some(Self::Equal),
            '~' => Some(Self::Not),
            _ => None,
        }
    }
    pub(crate) fn to_xml(self) -> String {
        util::xml_item("symbol", &self.char().to_string())
    }
}

/// a decimal integer
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct IntegerConstant(pub(crate) u16);

impl IntegerConstant {
    pub(crate) fn parse(word: &str) -> Option<Self> {
        word.parse().ok().map(Self)
    }
}

impl Display for IntegerConstant {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// a sequence of letters,digits,and underscore,not starting with a digit.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Identifier(String);
impl Identifier {
    pub(crate) fn parse(word: &str) -> Option<Self> {
        if !word.starts_with(char::is_numeric)
            && word
                .chars()
                .all(|c| c.is_numeric() || c.is_ascii_alphabetic() || c == '_')
        {
            Some(Self(word.into()))
        } else {
            None
        }
    }
    pub(crate) fn as_str(&self) -> &str {
        self.0.as_str()
    }
}
